// Copyright (C) 2009 Mihai Preda
  
package calculator;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.AdapterView;
import android.text.InputType;
import android.text.TextWatcher;
import android.text.Editable;
import android.view.KeyEvent;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.util.Log;
import android.content.res.Configuration;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;

import java.util.ArrayList;

import arity.calculator.plus.R;

import org.javia.arity.Util;
import org.javia.arity.*;

public class Calculator extends Activity
	implements
		TextWatcher,
		View.OnKeyListener, View.OnClickListener,
		AdapterView.OnItemClickListener,
		SharedPreferences.OnSharedPreferenceChangeListener
{
    static final char MINUS = '\u2212', TIMES = '\u00d7', DIV = '\u00f7', SQRT = '\u221a', PI = '\u03c0', 
        UP_ARROW = '\u21e7', DN_ARROW = '\u21e9', ARROW = '\u21f3',
        ENTER = '\u21B2', ANSWER = '\u25fb', BACKSPACE = '\u25C0';

    private static final String INFINITY = "Infinity";
    private static final String INFINITY_UNICODE = "\u221e";
    
    private static final KeyEvent KEY_DEL = new KeyEvent(0, KeyEvent.KEYCODE_DEL);

    static Symbols symbols = new Symbols();
    static Function function;

    private TextView result;
    private EditText input;
    private ListView historyView;
    private GraphView graphView;
    private Graph3dView graph3dView;
    private History history;
    private HistoryAdapter adapter;   
    private int nDigits = 0; // TODO: what is this used for?
    static ArrayList<Function> graphedFunction;
    private static Defs mDefinitions;
    private ArrayList<Function> auxFuncs = new ArrayList<Function>();
    static boolean mUseHighQuality3d = true;
    
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        
        history = new History(this);
        adapter = new HistoryAdapter(this, history);
        
        internalConfigChange(getResources().getConfiguration());        

        mDefinitions = new Defs(this, symbols);                   
        
        if (history.fileNotFound)
        {
        	String[] init = {"sqrt(pi)\u00f70.5!", "e^(i\u00d7pi)", "ln(e^100)", "sin(x)", "x^2"};
        	nDigits = 10;
        	
        	for (String s : init)
        		onEnter(s);

        	nDigits = 0;
        }
        
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
        preferences.registerOnSharedPreferenceChangeListener(this);
        
        String value = preferences.getString("quality", null);
        
        if (value == null)
        {
            value = "high";
            preferences.edit().putString("quality", value).commit();
        }
        
        mUseHighQuality3d = value.equals("high");   
    }    

    @Override
    public void onConfigurationChanged(Configuration config)
    {
        super.onConfigurationChanged(config);
        
        internalConfigChange(config);
    }

    private void internalConfigChange(Configuration config)
    {
        setContentView(R.layout.calculator_activity);
        
        graphView = (GraphView) findViewById(R.id.graph);
        graph3dView = (Graph3dView) findViewById(R.id.graph3d);
        historyView = (ListView) findViewById(R.id.history);             

        result = (TextView) findViewById(R.id.result);

        Editable oldText = (input != null ? input.getText() : null);
        
        input = (EditText) findViewById(R.id.input);
        input.setOnKeyListener(this);
        input.addTextChangedListener(this);
        input.setEditableFactory(new CalculatorEditable.Factory()); 
        input.setInputType(InputType.TYPE_NULL);
        input.setRawInputType(InputType.TYPE_CLASS_TEXT);
        
        changeInput(history.getText());
        
        if (oldText != null)
            input.setText(oldText);
        
        graphView.setOnClickListener(this);
        graph3dView.setOnClickListener(this);
        
        if (historyView != null)
        {
            historyView.setAdapter(adapter);
            historyView.setOnItemClickListener(this);
        }
    }
    
    @Override
    public void onPause()
    {
        super.onPause();
        
        graph3dView.onPause();
        
        history.updateEdited(input.getText().toString());
        history.save();
        
        getDefinitions().save();
    }

    @Override
    public void onResume()
    {
        super.onResume();
        
        graph3dView.onResume();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu)
    {
        super.onCreateOptionsMenu(menu);
        
        getMenuInflater().inflate(R.menu.main, menu);
        
        return true;
    }
    
    @Override
    public boolean onPrepareOptionsMenu(Menu menu)
    {
        super.onPrepareOptionsMenu(menu);
        
        menu.findItem(R.id.clear_history).setEnabled(history.size() > 0);
        menu.findItem(R.id.list_defs).setEnabled(getDefinitions().size() > 0);

        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item)
    {
    	super.onOptionsItemSelected(item);
    	int id = item.getItemId();
    	
    	switch (id)
    	{
    	case R.id.list_defs:
    		{
    			Intent intent = new Intent(this, ListDefs.class);
    			startActivity(intent);	    
    		}
    		break;

        case R.id.help:
            startActivity(new Intent(this, Help.class));
            break;

        case R.id.clear_history:
        	{
        		history.clear();
        		history.save();
        		adapter.notifyDataSetInvalidated();
        	}
            break;

        case R.id.clear_defs:
        	{
        		getDefinitions().clear();
        		getDefinitions().save();
        	}
            break;

        case R.id.settings:        
            startActivity(new Intent(this, Settings.class));
            break;

        default:
        	return false;
    	}
    	
    	return true;
    }

    @Override
    public void onSharedPreferenceChanged(SharedPreferences prefs, String key)
    {       
        if (key.equals("quality"))
            mUseHighQuality3d = prefs.getString(key, "high").equals("high");
    }

    @Override
    public void onClick(View target)
    {
        if (target == graphView || target == graph3dView)
            startActivity(new Intent(this, ShowGraph.class));
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int pos, long id)
    {
    	history.moveToPos(pos, input.getText().toString());
    	changeInput(history.getText());
    }
    
    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after)
    {
    	
    }    
    
    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count)
    {
    	
    }
    
    @Override
    public void afterTextChanged(Editable s)
    {
        evaluate();
    }   

    @Override
    public boolean onKey(View v, int keyCode, KeyEvent event)
    {
        int action = event.getAction();
        
        if (action == KeyEvent.ACTION_DOWN)
        {
            switch (keyCode)
            {
            case KeyEvent.KEYCODE_ENTER:
            case KeyEvent.KEYCODE_DPAD_CENTER:
                doEnter();
                break;
                
            case KeyEvent.KEYCODE_DPAD_UP:
                onUp();
                break;
                
            case KeyEvent.KEYCODE_DPAD_DOWN:            
                onDown();
                break;
                
            default:
                return false;
            }
            
            return true;
        }
        else
        {
            switch (keyCode)
            {
            case KeyEvent.KEYCODE_ENTER:
            case KeyEvent.KEYCODE_DPAD_CENTER:
            case KeyEvent.KEYCODE_DPAD_UP:
            case KeyEvent.KEYCODE_DPAD_DOWN:
                return true;
                
            default:
            	return false;
            }
        }
    }    

    static void log(String mes)
    {
    	Log.d("Calculator", mes);
    }

    void evaluate()
    {
        evaluate(input.getText().toString());
    }
    
    private String formatEval(Complex value)
    {
    	// TODO: nDigits and its purpose in complexToString()?
    	if (nDigits == 0)
            nDigits = getResultSpace();
    	
    	String res = Util.complexToString(value, nDigits, 2);
    	return res.replace(INFINITY, INFINITY_UNICODE);
    }

    private void evaluate(String text)
    {
        if (text.length() == 0)
        {
            result.setEnabled(false);
            return;
        }

        auxFuncs.clear();
        int end = -1;
        
        do
        {
            text = text.substring(end+1);
            end  = text.indexOf(';');
            
            String slice = end == -1 ? text : text.substring(0, end);
            
            try
            {
                Function f = symbols.compile(slice);
                auxFuncs.add(f);
            }
            catch (SyntaxException e)
            {
                continue;
            }
        }
        while (end != -1);
        
        graphedFunction = auxFuncs;
        
        int size = auxFuncs.size();
        
        if (size == 0)
        {
            result.setEnabled(false);
            return;
        }
        else if (size == 1)
        {
            Function f = auxFuncs.get(0);
            int arity = f.arity();

            if (arity == 1 || arity == 2)
            {
                result.setText(null);
                showGraph(f);
            }
            else if (arity == 0)
            {
                result.setText(formatEval(f.evalComplex()));
                result.setEnabled(true);
                
                showGraph(null);
            }
            else
            {
                result.setText("function");
                result.setEnabled(true);
                
                showGraph(null);
            }
        }
        else
        {
            graphView.setFunctions(auxFuncs);
            
            if (graphView.getVisibility() != View.VISIBLE)
            {
                result.setVisibility(View.GONE);
                historyView.setVisibility(View.GONE);
                
                graph3dView.setVisibility(View.GONE);                
                graph3dView.onPause();
                
                graphView.setVisibility(View.VISIBLE);                
            }
        }
    }

    private int getResultSpace()
    {
        int width = result.getWidth() - result.getTotalPaddingLeft() - result.getTotalPaddingRight();
        float oneDigitWidth = result.getPaint().measureText("5555555555") / 10f;
        return (int) (width / oneDigitWidth);
    }    

    private void showGraph(Function f)
    {
        if (f == null)
        {
            if (historyView.getVisibility() != View.VISIBLE)
            {
                graphView.setVisibility(View.GONE);
                graph3dView.setVisibility(View.GONE);
                graph3dView.onPause();
                
                historyView.setVisibility(View.VISIBLE);
                result.setVisibility(View.VISIBLE);
            }
        }
        else
        {
            if (f.arity() == 1)
            {
                graphView.setFunction(f);
                
                if (graphView.getVisibility() != View.VISIBLE)
                {
                    result.setVisibility(View.GONE);
                    historyView.setVisibility(View.GONE);
                    
                    graph3dView.setVisibility(View.GONE);
                    graph3dView.onPause();
                    
                    graphView.setVisibility(View.VISIBLE);
                }
            }
            else
            {
                graph3dView.setFunction(f);
                
                if (graph3dView.getVisibility() != View.VISIBLE)
                {
                    result.setVisibility(View.GONE);
                    historyView.setVisibility(View.GONE);                    
                    graphView.setVisibility(View.GONE);
                    
                    graph3dView.setVisibility(View.VISIBLE);
                    graph3dView.onResume();
                }
            }
        }
    }

    void onEnter()
    {
    	onEnter(input.getText().toString());
    }

    void onEnter(String text)
    {
    	boolean historyChanged = false;
    	
    	try
    	{
    		FunctionAndName fan = symbols.compileWithName(text);
    		
    		if (fan.name != null)
    		{
    			symbols.define(fan);
    			getDefinitions().add(text);
    		}
    		
    		Function f = fan.function;    		
            int arity = f.arity();            
            Complex value = null;
            
            if (arity == 0)
            {
                value = f.evalComplex();
                symbols.define("ans", value);
            }
            
            historyChanged = arity == 0
            	? history.onEnter(text, formatEval(value))
            	: history.onEnter(text, null);
    	}
    	catch (SyntaxException e)
    	{
    		historyChanged = history.onEnter(text, null);
    	}
    	
        showGraph(null);
        
        if (historyChanged)
            adapter.notifyDataSetInvalidated();
        
        if (text.length() == 0)
            result.setText(null);
        
        changeInput(history.getText());
    }
    
    private void changeInput(String newInput)
    {
        input.setText(newInput);
        input.setSelection(newInput.length());
    }    

    void onUp()
    {
        if (history.moveUp(input.getText().toString()))
            changeInput(history.getText());
    }

    void onDown()
    {
        if (history.moveDown(input.getText().toString()))
            changeInput(history.getText());
    }   

    void doEnter()
    {
        onEnter();
    }

    void doBackspace()
    {
        input.dispatchKeyEvent(KEY_DEL);
    }

	public static Defs getDefinitions()
	{
		return mDefinitions;
	}
	
	public void onSoftKey(View view)
	{
		switch (view.getId())
		{
		case R.id.kb_control_enter:
			doEnter();
			break;
			
		case R.id.kb_control_backspace:
			doBackspace();
			break;
			
		case R.id.kb_control_alpha:
			{
				View fragmentView = getFragmentManager().findFragmentById(R.id.keyboard_qwerty).getView();
				int action = (fragmentView.getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE);
				
				fragmentView.setVisibility(action);								
			}
			break;
			
		default:
			{
				CharSequence text = ((TextView) view).getText();
				input.getText().insert(input.getSelectionStart(), text);
			}
			break;
		}
	}
}
